/**
 * @author : bruce <bruceli1688@gmail.com>
 * @since : 2014-09-25
 * @filename : sea-modules/candy/candy.js
 * @version : 1.0
 * @description : new mobile
 */
(function(Strophe, $){

    var NAME_SEPARATOR = '__';

    var MiniIM = (function(self, Strophe, $) {

        var _connection = null,

            _service = null,

            _domain = null,

            _sessionId = null,

            _addNamespace = function(name, value) {
                Strophe.addNamespace(name, value);
            },

            _addNamespaces = function() {
                _addNamespace('PRIVATE', 'jabber:iq:private');
                _addNamespace('DELAY', 'jabber:x:delay');
                _addNamespace('ARCHIVE', 'urn:xmpp:archive');
                _addNamespace('TIME', 'urn:xmpp:time');
            },

            _registerEventHandlers = function() {
                //self.addHandler(self.Handler.Version, Strophe.NS.VERSION, 'iq');
                self.addHandler(self.Handler.Jabber.Presence, null, 'presence');
                self.addHandler(self.Handler.Jabber.Message, null, 'message');
                self.addHandler(self.Handler.Jabber.Bookmarks, Strophe.NS.PRIVATE, 'iq');
                self.addHandler(self.Handler.Jabber.Archive, Strophe.NS.ARCHIVE, 'iq', 'result');
                self.addHandler(self.Handler.Jabber.Time, Strophe.NS.TIME, 'iq', 'result');
                //self.addHandler(self.Handler.StreamError, null, 'stream:error');
            },

            _getEscapedJidFromJid = function(jid) {
                var node = Strophe.getNodeFromJid(jid),
                    domain = Strophe.getDomainFromJid(jid);
                return node ? Strophe.escapeNode(node) + '@' + domain : domain;
            };

        self.TYPE = {
            ROOM: 1,              // public chat room
            PRIVATE: 2,       // personal private chat
            GROUP: 3             // group user created
        };

        self.init = function(service, domain, sessionId) {
            _service = service;
            _domain = domain;
            _sessionId = sessionId;
            _addNamespaces();

            _connection = new Strophe.Connection(_service);
            _connection.rawInput = self.rawInput.bind(self);
            _connection.rawOutput = self.rawOutput.bind(self);

            window.onbeforeunload = self.onWindowUnload;

            self.log = function(str) {
                try { // prevent erroring
                    if(typeof window.console !== undefined && typeof window.console.log !== undefined) {
                        //console.log(str);
                    }
                } catch(e) {
                    //console.error(e);
                }
            };
            self.log('[Init] Debugging enabled');
        };

        self.connect = function(jidOrHost, password, nick) {
            // Reset before every connection attempt to make sure reconnections work after authfail, alltabsclosed, ...
            _connection.reset();
            _registerEventHandlers();

            if(jidOrHost && password) {
                // authentication
                _connection.connect(_getEscapedJidFromJid(jidOrHost) + '/MiniIM', password, MiniIM.Handler.Strophe.Connect);
            } else if(jidOrHost && nick) {
                // anonymous connect
                _connection.connect(_getEscapedJidFromJid(jidOrHost) + '/MiniIM', null, MiniIM.Handler.Strophe.Connect);
            } else {
                alert('Please provide connect username and password to login');
            }
        };

        self.attach = function(jid, sid, rid) {
            _connection.reset();
            _registerEventHandlers();
            _connection.attach(jid, sid, rid, MiniIM.Handler.Strophe.Connect);
        };

        self.disconnect = function() {
            if(_connection.connected) {
                _connection.disconnect();
            }
        };

        self.getConnection = function() {
            return _connection;
        };

        self.getDomain = function() {
            return _domain;
        };

        self.getOwnJid = function() {
            return _connection.jid;
        };

        self.getSessionId = function() {
            return _sessionId;
        };

        self.addHandler = function(handler, ns, name, type, id, from, options) {
            return _connection.addHandler(handler, ns, name, type, id, from, options);
        };

        self.onWindowUnload = function() {
            // Enable synchronous requests because Safari doesn't send asynchronous requests within unbeforeunload events.
            // Only works properly when following patch is applied to strophejs: https://github.com/metajack/strophejs/issues/16/#issuecomment-600266
            _connection.sync = true;
            self.disconnect();
            _connection.flush();
        };

        self.rawInput = function(data) {
            this.log('RECV: ' + data);
        };

        self.rawOutput = function(data) {
            this.log('SENT: ' + data);
        };

        return self;

    }(MiniIM || {}, Strophe, Zepto));


    MiniIM.Action = (function(self, Strophe, $) {

        self.joinRoom = function(roomName, password) {
            var ownerJid = MiniIM.getOwnJid();
            var roomJid = roomName + '@conference.' + Strophe.getDomainFromJid(ownerJid);
            MiniIM.getConnection().send(
                $iq({type: 'get', from: MiniIM.getOwnJid(), to: roomJid, id: 'disco3'}).
                c('query', {xmlns: Strophe.NS.DISCO_INFO}).tree());
            MiniIM.getConnection().muc.join(roomJid, MiniIM.Util.getRealName(ownerJid), null, null, password);
        };

        self.sendMessage = function(receiver, message, type, params) {
            message = $.trim(message);
            type = type || 'chat';
            if(message === '' || receiver == undefined || receiver === '') {
                return false;
            }

            var msgid = MiniIM.getConnection().getUniqueId();
            var uniqueId = params['uniqueId'] || 'noset';
            if (type === 'chat') {
                var msg = $msg({to: MiniIM.Util.toJid(receiver), from: MiniIM.getOwnJid(), type: 'chat', id: msgid, flag: uniqueId}).
                    c('body', {xmlns: Strophe.NS.CLIENT}).t(message);
                if (params['senderId']) {
                    msg.up().c("senderid").t(params['senderId']);
                }
                if (params['receiverId']) {
                    msg.up().c("receiverid").t(params['receiverId']);
                }

                MiniIM.getConnection().send(msg);
            } else if (type === 'groupchat') {
                var roomJid = receiver + '@conference.' + Strophe.getDomainFromJid(MiniIM.getOwnJid());
                //MiniIM.getConnection().muc.message(roomJid, undefined, message, type);
                var msg = $msg({to: roomJid, from: MiniIM.getOwnJid(), type: 'groupchat', id: MiniIM.getConnection().getUniqueId(), flag: uniqueId}).
                    c("body", {xmlns: Strophe.NS.CLIENT}).t(message);
                msg.up().c("x", {xmlns: "jabber:x:event"}).c("composing");

                MiniIM.getConnection().send(msg);
            }

            return true;
        };

        self.loadArchive = function(name, id, type, timepoint, count) {
            var params = {xmlns: Strophe.NS.ARCHIVE};
            switch(type) {
                case MiniIM.TYPE.ROOM:
                    params['with'] = name;
                    break;
                case MiniIM.TYPE.PRIVATE:
                    params['with'] = id;
                    params['name'] = name;
            }
            if (timepoint != undefined) {
                params['timepoint'] = timepoint;
            }
            if (count != undefined) {
                params['count'] = count;
            }
            MiniIM.getConnection().send($iq({type: 'get', from: MiniIM.getOwnJid()}).c('history', params));
            
        };

        self.loadRecentContacts = function() {
            MiniIM.getConnection().send($iq({type: 'get', from: MiniIM.getOwnJid()})
                .c('contact', {xmlns: Strophe.NS.ARCHIVE, 'recent': '1'}));
        }

        self.loadOnlineUserList = function() {
            MiniIM.getConnection().send($iq({type: 'get', from: MiniIM.getOwnJid()})
                .c('online-user', {xmlns: Strophe.NS.ARCHIVE}));
        }

        self.removeArchive = function(name) {
            MiniIM.getConnection().send($iq({type: 'get', from: MiniIM.getOwnJid()})
                .c('history', {xmlns: Strophe.NS.ARCHIVE, 'with': MiniIM.Util.toJid(name), 'clear': 1}));
        }

        self.markMessageReaded = function(messageId) {
            MiniIM.getConnection().send($iq({type: 'get', from: MiniIM.getOwnJid()})
                .c('unread', {xmlns: Strophe.NS.ARCHIVE, 'messageid': messageId}));
        };

        self.markAllMessagesRead = function(name) {
            MiniIM.getConnection().send($iq({type: 'get', from: MiniIM.getOwnJid()})
                .c('unread', {xmlns: Strophe.NS.ARCHIVE, 'with': MiniIM.Util.toJid(name)}));
        }

        self.loadUnreadMessages = function() {
            MiniIM.getConnection().send($iq({type: 'get', from: MiniIM.getOwnJid()})
                .c('unread', {xmlns: Strophe.NS.ARCHIVE, 'list': '1'}));
        };

        self.registerSessionId = function(sessionId) {
            MiniIM.getConnection().send($iq({type: 'get', from: MiniIM.getOwnJid()})
            .c('query', {xmlns: Strophe.NS.PRIVATE}).c('properties', {xmlns: 'chat:user:properties', 'sessionid': sessionId}).tree());
        };

        self.serverTime = function() {
            MiniIM.getConnection().send($iq({type: 'get', from: MiniIM.getOwnJid()}).
                c('time', {xmlns: Strophe.NS.TIME}).tree());
        }

        return self;

    }(MiniIM.Action || {}, Strophe, Zepto));


    MiniIM.Handler = (function(self, Strophe, $) {

        var _registeredHandlers = {};

        self.KEYS = {
            CONN_STATUS: 1,              // for connection status changed
            PRESENCE: 2,                       // for chat room presence event
            MESSAGE: 3,                         // for message event
            ARCHIVE: 4,                           // for archive loading event
            ERROR: 5,                                // for error message handler
            RECENT_CONTACTS: 6,   // for getting recent contacts
            UNREAD: 7,                             // for unread messages
            ONLINE_USERS: 8,             // for online user list
            TIME: 9                                     // for time sync
        };

        self.removeAllHandler = function() {
            _registeredHandlers = {};
        };

        self.addHandler = function(key, handler) {
            if (_registeredHandlers[key] === undefined) {
                _registeredHandlers[key] = [];
            }
            _registeredHandlers[key].push(handler);
        };

        self.removeHandler = function(key, handler) {
            delete _registeredHandlers[key][handler];
        };

        self.notifyHandlers = function(key, arg) {
            var handlers = _registeredHandlers[key], i;
            if (handlers != undefined) {
                for(i = handlers.length-1; i >= 0; i--) {
                    handlers[i].proccess(self, arg);
                }
            }
        };


        self.Strophe = {

            Connect: function(status, condition) {
                switch(status) {
                    case Strophe.Status.CONNECTED:
                        MiniIM.log('[Connection] Connected');
                    
                    case Strophe.Status.ATTACHED:
                        MiniIM.log('[Connection] Attached');
                        MiniIM.getConnection().send($pres().tree());
                        if (MiniIM.getSessionId() != null) {
                            MiniIM.Action.registerSessionId(MiniIM.getSessionId());
                        }
                        break;

                    case Strophe.Status.DISCONNECTED:
                        MiniIM.log('[Connection] Disconnected');
                        break;

                    case Strophe.Status.AUTHFAIL:
                        MiniIM.log('[Connection] Authentication failed');
                        break;

                    case Strophe.Status.CONNECTING:
                        MiniIM.log('[Connection] Connecting');
                        break;

                    case Strophe.Status.DISCONNECTING:
                        MiniIM.log('[Connection] Disconnecting');
                        break;

                    case Strophe.Status.AUTHENTICATING:
                        MiniIM.log('[Connection] Authenticating');
                        break;

                    case Strophe.Status.ERROR:
                    case Strophe.Status.CONNFAIL:
                        MiniIM.log('[Connection] Failed (code: ' + status + ', reason: ' + condition + ')');
                        break;

                    default:
                        MiniIM.log('[Connection] Unkown');
                }

                self.notifyHandlers(self.KEYS.CONN_STATUS, { status: status,  condition: condition } );
            }

        };

        self.Jabber = {
            Presence: function(msg) {
                MiniIM.log('[Jabber] Presence');
                try {
                    msg = $(msg);
                    if (typeof(msg.children('x').attr('xmlns')) !== 'undefined' && msg.children('x').attr('xmlns').indexOf(Strophe.NS.MUC) > -1) {
                        var presenceType = msg.attr('type'),
                            nick = msg.find('nick').text(),
                            name = msg.find('name').text();
                        if (presenceType !== 'error') {
                            var nameFromJid = MiniIM.Util.getRealName(msg.attr('from'));
                            self.notifyHandlers(self.KEYS.PRESENCE, { 
                                'fromJid': msg.attr('from'), 
                                'nick': nick || nameFromJid,
                                'name' : name || nameFromJid,
                                'type':  presenceType === 'unavailable' ? 'leave' : 'join'
                            });
                        }
                    }
                } catch(ex) {
                    MiniIM.log('Exception on MiniIM.Handler.Jabber.Presence: ' + ex.message);
                    MiniIM.log(ex.stack);
                }
                return true;
            },

            Message: function(msg) {
                MiniIM.log('[Jabber] Message');

                try {
                    var msg = $(msg),
                        fromJid = msg.attr('from'),
                        type = msg.attr('type');

                    if (fromJid !== Strophe.getDomainFromJid(fromJid)) {
                        if (type === 'error') {
                            var error = msg.children('error');
                            var unqueId = msg.attr('flag');

                            self.notifyHandlers(self.KEYS.ERROR, { 
                                'fromJid': fromJid,
                                'uniqueId': unqueId,
                                'code': error.attr('code'),
                                'message' : error.text()
                            });
                        } else if ((type === 'groupchat' || type === 'chat') &&
                                        msg.children('body').length > 0) {
                            var sender = MiniIM.Util.getRealName(fromJid);
                            var message = msg.children('body').text();
                            var messageId = msg.find('messageid').text();
                            var timestamp = msg.find('timestamp').text();
                            if (msg.find('delay').length > 0) {
                                timestamp = msg.find('delay').attr('stamp');
                            }

                            var params = { 
                                'fromJid': fromJid, 
                                'sender': sender, 
                                'type': type,
                                'message': message,
                                'messageId': messageId,
                                'time': timestamp || new Date().toGMTString()
                            };
                            if (msg.attr('internal') == 'true') {
                                params['system'] = 'true';
                                params['sysType'] = msg.find('ext-info').children('type').text();
                                params['sysMsgId'] = msg.find('ext-info').children('msgid').text();
                                params['sysLink'] = msg.find('ext-info').children('link').text();
                                if (msg.find('ext-info').children('sender').length > 0) {
                                    params['sysSender'] = msg.find('ext-info').children('sender').text();
                                }
                            }
                            self.notifyHandlers(self.KEYS.MESSAGE, params);
                        }
                    }
                } catch(ex) {
                    MiniIM.log('Exception on MiniIM.Handler.Jabber.Message: ' + ex.message);
                    MiniIM.log(ex.stack);
                }

                return true;
            },

            Archive: function(iq) {
                try {
                    if ($(iq).children('history').length > 0) {
                        MiniIM.log('[Jabber] Archive');

                        var messages = [];
                        var withJid = $(iq).children('history').attr('with');
                        var timepoint = null;
                        $(iq).find('message').each( function() {
                            messages.push(MiniIM.Util.parseMessageStanza($(this)));
                        });
                        if (messages.length > 0) {
                            timepoint = $(iq).find('message').first().find('time').text();
                        }

                        self.notifyHandlers(self.KEYS.ARCHIVE, { 'jid': withJid, 'timepoint': timepoint, 'count': messages.length, 'messages': messages } );
                    } else if ($(iq).children('unread').length > 0 && $(iq).children('unread').attr('list') != undefined) {
                        MiniIM.log('[Jabber] Unread messages');

                        var unreadMap = {};
                        $(iq).find('message').each( function() {
                            var msg = MiniIM.Util.parseMessageStanza($(this));
                            if (unreadMap[msg.sender] == undefined) {
                                unreadMap[msg.sender] = [];
                            }
                            unreadMap[msg.sender].push(msg);
                        });

                        self.notifyHandlers(self.KEYS.UNREAD, { 'count': $(iq).find('message').length, 'messages': unreadMap });
                    } else if ($(iq).children('contact').length > 0 && $(iq).children('contact').attr('recent') != undefined) {
                        MiniIM.log('[Jabber] Receive recent contacts');

                        var contactMap = {};
                        $(iq).find('contactitem').each( function() {
                            var msg = MiniIM.Util.parseMessageStanza($(this));
                            contactMap[$(this).children('name').text()] = msg;
                        });

                        self.notifyHandlers(self.KEYS.RECENT_CONTACTS, { 'contacts': contactMap });
                    } else if ($(iq).children('online-user').length > 0) {
                        MiniIM.log('[Jabber] Receive chat room online user list');
                        var users = $(iq).children('online-user').text().split(',');

                        self.notifyHandlers(self.KEYS.ONLINE_USERS, { 'room': MiniIM.getDomain(), 'list': users });
                    }
                } catch(ex) {
                    MiniIM.log('Exception on MiniIM.Handler.Jabber.Archive: ' + ex.message);
                    MiniIM.log(ex.stack);
                }

                return true;
            },

            Bookmarks: function(iq) {
                MiniIM.log('[Jabber] Bookmarks');

                return true;
            },

            Time: function(iq) {
                MiniIM.log('[Jabber] Time');

                try {
                    var tz = $(iq).find('tzo').text();
                    var time = $(iq).find('utc').text();

                    self.notifyHandlers(self.KEYS.TIME, { 'timezone': tz, 'timestamp': time });
                } catch(ex) {
                    MiniIM.log('Exception on MiniIM.Handler.Jabber.Time: ' + ex.message);
                    MiniIM.log(ex.stack);
                }
            }

        };


        return self;

    }(MiniIM.Handler || {}, Strophe, Zepto));


    MiniIM.Util = (function(self, $){
        
        self.getRealName = function(jid) {
            var realName = '';

            if (jid.indexOf('@conference.') > 0 && jid.indexOf('/') > 0) {
                // For conference jid, get resource part of jid
                realName = Strophe.getResourceFromJid(jid);
            } else {
                // For normal jid, get node part of jid
                realName = self.removeDomainFromUsername(Strophe.getNodeFromJid(jid));
            }

            return realName;
        };

        self.toJid = function(username) {
            return username.toLowerCase() + NAME_SEPARATOR + MiniIM.getDomain() + '@' + Strophe.getDomainFromJid(MiniIM.getOwnJid());
        };

        self.toConferenceJid = function(roomName) {
            return roomName + '@conference.' + Strophe.getDomainFromJid(MiniIM.getOwnJid());
        };

        self.parseMessageStanza = function(msg) {
            var sender = MiniIM.Util.removeDomainFromUsername(msg.children('sender').text());
            var message = msg.find('body').text();
            var id = msg.find('id').text();
            var time = msg.find('time').text();
            var messageRecord = {'id': id, 'sender': sender, 'message': message, 'time': time};
            if (msg.find('ext-info').length > 0) {
                messageRecord['system'] = 'true';
                messageRecord['sysType'] = msg.find('ext-info').children('type').text();
                messageRecord['sysMsgId'] = msg.find('ext-info').children('msgid').text();
                messageRecord['sysLink'] = msg.find('ext-info').children('link').text();
                messageRecord['sysSender'] = msg.find('ext-info').children('sender').text();
            }

            return messageRecord;
        }

        self.removeDomainFromUsername = function(username) {
            if (!username)
                return '';

            var separatorIdx = username.lastIndexOf(NAME_SEPARATOR);
            if (separatorIdx > 0) {
                return username.substring(0, separatorIdx);
            }

            return username;
        };

        self.getDomainFromUsername = function(username) {
            var separatorIdx = username.lastIndexOf(NAME_SEPARATOR);
            if (separatorIdx > 0) {
                return username.substring(separatorIdx + 2);
            }

            return '';
        };

        self.localizedTime = function(dateTime) {
            if (dateTime === undefined) {
                return undefined;
            }

            var date = self.iso8601toDate(dateTime);
            if(date.toDateString() === new Date().toDateString()) {
                return date.format('h:MMTT');
            } else {
                return date.format('mmm d, h:MMTT');
            }
        };

        self.iso8601toDate = function(date) {
            var timestamp = Date.parse(date), minutesOffset = 0;
            if(isNaN(timestamp)) {
                var struct = /^(\d{4}|[+\-]\d{6})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?))?/.exec(date);
                if(struct) {
                    if(struct[8] !== 'Z') {
                        minutesOffset = +struct[10] * 60 + (+struct[11]);
                        if(struct[9] === '+') {
                            minutesOffset = -minutesOffset;
                        }
                    }
                    return new Date(+struct[1], +struct[2] - 1, +struct[3], +struct[4], +struct[5] + minutesOffset, +struct[6], struct[7] ? +struct[7].substr(0, 3) : 0);
                } else {
                    // XEP-0091 date
                    timestamp = Date.parse(date.replace(/^(\d{4})(\d{2})(\d{2})/, '$1-$2-$3') + 'Z');
                }
            }
            return new Date(timestamp);
        };

        return self;

    }(MiniIM.Util || {}, Zepto));


    if (typeof define === "function" ) {
        define("candy/candy.js", [], function () { return MiniIM;} );
    }

})(Strophe, Zepto);

